package cn.bearspark.happening.provider.dao.buffer;

import cn.bearspark.common.dto.Page;
import cn.bearspark.common.enumeration.VisibilityEnum;
import cn.bearspark.framework.redis.starter.buffer.list.RedisBaseListBuffer;
import cn.bearspark.happening.provider.dao.key.HappeningBufferKeyBuilder;
import cn.bearspark.happening.provider.dao.mapper.HappeningBaseMapper;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 分页查询动态 id 的缓存器
 *
 * @author f
 */
@Component
public class HappeningIdPageBuffer extends RedisBaseListBuffer<Long> {

    /**
     * 分页查询信息的缓存的有效期（单位：s），默认是 5min
     */
    private static final int PAGE_CACHE_EXPIRE = 60 * 5;

    private final HappeningBaseMapper happeningBaseMapper;

    private final HappeningBufferKeyBuilder happeningBufferKeyBuilder;

    public HappeningIdPageBuffer(RedisTemplate<String, Object> redisTemplate,
                                 HappeningBaseMapper happeningBaseMapper,
                                 HappeningBufferKeyBuilder happeningBufferKeyBuilder) {
        super(redisTemplate);
        this.happeningBaseMapper = happeningBaseMapper;
        this.happeningBufferKeyBuilder = happeningBufferKeyBuilder;
    }

    /**
     * 分页查询指定用户的动态 id 集合
     *
     * @param operatorId 执行查询操作的用户 id
     * @param userId     指定的用户 id
     * @param pageNum    页数
     * @param pageSize   元素数
     * @return 指定用户的动态 id 集合
     */
    public List<Long> pageUserHappeningIdList(long operatorId, long userId, int pageNum, int pageSize) {
        int start = (pageNum - 1) * pageSize;
        int end = start + pageSize - 1;

        int visCode;
        if (userId == operatorId) {
            // 当用户自己查询时，可以查询到仅自己可见的动态
            visCode = VisibilityEnum.PRIVATE.getCode();
        } else {
            // 当其它用户查询时，只能查询到公开的动态
            visCode = VisibilityEnum.PUBLIC.getCode();
        }

        return super.range(
                happeningBufferKeyBuilder.buildUserHappeningPageInfoKey(userId, visCode),
                start, end,
                () -> happeningBaseMapper.listUserHappeningsId(userId, start, pageSize, visCode)
        );
    }

    /**
     * 分页查询自己全部动态的 id
     *
     * @param userId         查询自己全部动态的用户 id
     * @param followerIdList 该用户的关注者的用户 id 集合
     * @param pageNum        页数
     * @param pageSize       元素数
     * @return 自己全部动态的 id 的分页查询结果
     */
    public Page<Long> pageSelfHappeningIdList(long userId, List<Long> followerIdList, int pageNum, int pageSize) {
        String idListKey = happeningBufferKeyBuilder.buildHappeningPageInfoKey(userId);
        String totalKey = happeningBufferKeyBuilder.buildHappeningTotalInfoKey(userId);
        int pageOffset = getPageOffset(userId, pageNum, idListKey, totalKey);
        Page<Long> happeningIdPage = new Page<>();
        happeningIdPage.setTotal(getTotal(userId, followerIdList, pageOffset, totalKey));
        happeningIdPage.setRecords(listHappeningId(userId, followerIdList, pageNum, pageSize, pageOffset, idListKey));
        return happeningIdPage;
    }

    // 获取分页查询的偏移量
    private int getPageOffset(long userId, int pageNum, String idListCacheKey, String totalKey) {
        String pageOffsetCacheKey = happeningBufferKeyBuilder.buildPageOffsetKey(userId);
        Integer pageOffset = (Integer) redisTemplate.opsForValue().get(pageOffsetCacheKey);
        if (pageOffset == null) {
            return 0;
        }

        // 如果不是第一页，则直接返回偏移量即可
        if (pageNum != 1) {
            return pageOffset;
        }

        // 如果是第一页，则删除本用户的所有分页动态缓存，并且移除 redis 中缓存的偏移量
        redisTemplate.delete(List.of(
                idListCacheKey,         // 分页 id 的缓存
                totalKey,               // 动态总数的缓存
                pageOffsetCacheKey      // 偏移量的缓存
        ));

        // 最后的偏移量为 0
        return 0;
    }

    // 获取动态总数
    private Integer getTotal(long userId, List<Long> followerIdList, int pageOffset, String totalKey) {
        Integer total = (Integer) redisTemplate.opsForValue().get(totalKey);

        // 如果存在动态总数，则直接返回
        if (total != null) {
            return total;
        }

        // 如果不存在动态总数，则在 mysql 中查询
        total = CollectionUtils.isEmpty(followerIdList) ?
                happeningBaseMapper.countSelfHappenings(userId, VisibilityEnum.PRIVATE.getCode()) :
                happeningBaseMapper.countUserHappenings(userId, followerIdList,
                        VisibilityEnum.PUBLIC.getCode(), VisibilityEnum.PRIVATE.getCode());
        total -= pageOffset;

        // 并把查询到的数据缓存到 redis 中
        redisTemplate.opsForValue().set(totalKey, total, PAGE_CACHE_EXPIRE, TimeUnit.SECONDS);

        return total;
    }

    // 获取分页动态 id 集合
    private List<Long> listHappeningId(long userId, List<Long> followerIdList, int pageNum, int pageSize,
                                       int pageOffset, String idListKey) {
        int start = (pageNum - 1) * pageSize;
        int end = start + pageSize - 1;

        return super.range(idListKey, start, end, () -> CollectionUtils.isEmpty(followerIdList) ?
                happeningBaseMapper.listUserHappeningsId(userId, pageOffset + start, pageSize,
                        VisibilityEnum.PRIVATE.getCode()) :
                happeningBaseMapper.listSAFHappeningsId(userId, followerIdList, pageOffset + start, pageSize,
                        VisibilityEnum.PUBLIC.getCode(), VisibilityEnum.PRIVATE.getCode())
        );
    }

    /**
     * 增加指定用户的分页查询动态缓存的偏移量
     *
     * @param userIdList 待更新分页偏移量的用户 id 集合
     * @param userId     对动态做出操作的用户 id
     */
    public void incrPageOffset(List<Long> userIdList, long userId) {
        super.executeScript("script/incrPageOffset.lua", List.of(),
                PAGE_CACHE_EXPIRE,
                happeningBufferKeyBuilder.getPageOffsetKeyPrefix(),
                happeningBufferKeyBuilder.buildUserHappeningPageInfoKeyPrefix(userId),
                happeningBufferKeyBuilder.getHappeningPageInfoKeyPrefix(),
                userIdList.toArray(new Long[0])
        );
    }

    /**
     * 减少指定用户的分页查询动态缓存的偏移量
     *
     * @param userIdList  用户 id 集合
     * @param happeningId 修改的动态 id
     * @param userId      对动态做出操作的用户 id
     */
    public void decrPageOffset(List<Long> userIdList, long happeningId, long userId) {
        super.executeScript("script/decrPageOffset.lua", List.of(),
                PAGE_CACHE_EXPIRE,
                happeningBufferKeyBuilder.getPageOffsetKeyPrefix(),
                happeningBufferKeyBuilder.buildUserHappeningPageInfoKeyPrefix(userId),
                happeningBufferKeyBuilder.getHappeningPageInfoKeyPrefix(),
                happeningId,
                userIdList.toArray(new Long[0])
        );
    }
}
